import {load} from 'cheerio';
import he from 'he';
import {
    FilterCategory,
    FilterOption,
    FilterParameter,
    FilterCategoryType,
    DiscoverBook,
    BookLink,
    PageResult,
    DiscoverSource,
} from '../crawler/model/discover.js'
import {AbstractBookCrawler, BookCrawlerTest} from "../crawler/abstract_book_crawler.js";
import {AbstractDiscoverCrawler, DiscoverCrawlerTest} from "../crawler/abstract_discover_crawler.js";
import {
    BasicBook,
    Book,
    BasicChapter,
    SearchBookResult,
    BookSource,
} from '../crawler/model/book.js'

import {
    RequestOptions,
    RequestType,
    RequestMethod,
    DeviceType,
    RequestResponse,
    RequestInputType,
    GetCookiesOptions,
} from './model/request.js'

// 判的是否为node环境
// const isNode = typeof global !== 'undefined' && global === globalThis;

const userAgentPool = [
    // Chrome (Desktop)
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",

    // Firefox (Desktop)
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0",

    // Safari (Desktop)
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15",

    // Edge (Desktop)
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.59",

    // Opera (Desktop)
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 OPR/77.0.4054.203",

    // Chrome (Android)
    "Mozilla/5.0 (Linux; Android 10; SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36",

    // Firefox (Android)
    "Mozilla/5.0 (Android 10; Mobile; rv:89.0) Gecko/89.0 Firefox/89.0",

    // Safari (iPhone)
    "Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Mobile/15E148 Safari/604.1",

    // Edge (Android)
    "Mozilla/5.0 (Linux; Android 10; SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36 EdgA/46.03.4.5155",

    // Opera (Android)
    "Mozilla/5.0 (Linux; Android 10; SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36 OPR/62.3.3146.57763",

    // Internet Explorer 11 (Desktop)
    "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; AS; rv:11.0) like Gecko",

    // Chrome (iPad)
    "Mozilla/5.0 (iPad; CPU OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/91.0.4472.80 Mobile/15E148 Safari/604.1",

    // Samsung Internet (Android)
    "Mozilla/5.0 (Linux; Android 10; SAMSUNG SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.0 Chrome/87.0.4280.141 Mobile Safari/537.36",

    // UC Browser (Android)
    "Mozilla/5.0 (Linux; U; Android 10; en-US; SM-A205U Build/QP1A.190711.020) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 UCBrowser/13.3.2.1303 Mobile Safari/537.36",

    // Brave (Desktop)
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Brave/1.26.74"
];

const UserAgentKey = 'User-Agent'

/**
 * 随机获取一个userAgent
 */
function randomUserAgent() {
    const index = Math.floor(Math.random() * userAgentPool.length);
    return userAgentPool[index];
}

/**
 * 随机获取一个桌面设备的UserAgent
 */
function randomPCUserAgent() {
    const pcUserAgents = userAgentPool.filter(ua => !/(Mobile|Android|iPhone|iPad)/i.test(ua));
    const index = Math.floor(Math.random() * pcUserAgents.length);
    return pcUserAgents[index];
}

/**
 * 随机获取一个移动设备的UserAgent
 */
function randomMobileUserAgent() {
    const mobileUserAgents = userAgentPool.filter(ua => /(Mobile|Android|iPhone|iPad)/i.test(ua));
    const index = Math.floor(Math.random() * mobileUserAgents.length);
    return mobileUserAgents[index];
}

/**
 * html字符串转字符串，先把br转\n，再移除所有标签，最后进行转义
 * @param {*} html
 * @returns
 */
function htmlToString(html) {
    if (!html) {
        return html;
    }

    let result = html.replace(/<br\/?>/g, "\n")
        .replace(/( *\n+ *)+/g, "\n")
        .trim();
    result = result.replace(/<\/?[^>]+(>|$)/g, "");
    return he.decode(result).trim();
}

/**
 * 删除空白行和前后空白
 * @param str 原字符串
 * @returns
 */
function removeEmptyLines(str) {
    // 使用正则表达式删除空白行
    return str?.replace(/^\s*[\r\n]/gm, '').trim();
}

/**
 * 删除包含指定内容的行，并删除空白行
 * @param str 原字符串
 * @param contents 包含的内容，可以是字符串或者是字符串数组
 * @returns
 */
function removeLinesContaining(str, contents) {
    if (!str) {
        return str;
    }

    // 如果 contents 不是数组，将其转换为数组
    if (!Array.isArray(contents)) {
        contents = [contents];
    }

    // 遍历 contents 数组，依次删除包含每个内容的行
    contents.forEach(content => {
        // 创建正则表达式，匹配包含指定内容的行
        const regex = new RegExp(`^.*${content}.*$`, 'gm');
        str = str.replace(regex, '');
    });

    return removeEmptyLines(str);
}

/**
 * 通过分隔符分割字符串，移除index前面或者后面的所有元素，然后再通过分隔符join，返回处理好的字符串
 * @param {*} str 待处理的字符串
 * @param {*} separator 分隔符
 * @param {*} index 位置
 * @param {*} isBefore 是否是前面，否则是后面，默认false
 * @returns string
 */
function removeBasedOnSeparator(str, separator, index, isBefore) {
    const parts = str.split(separator);

    // 如果 index 为负数，从末尾开始计数
    if (index < 0) {
        index = parts.length + index;
    }

    if (isBefore) {
        return parts.slice(index + 1).join(separator);
    } else {
        return parts.slice(0, index).join(separator);
    }
}

/**
 * 获取基础url，如 https://example.com:8443/name=free#a，返回 https://example.com:8443
 * @param urlString
 * @returns {*|null}
 */
function getBaseUrl(urlString) {
    const match = urlString.match(/^(https?:\/\/[^/?#]+)/i);
    return match ? match[1] : null;
}

/**
 * 获取url的path
 * @param {*} url url字符串
 * @returns
 */
function getPathFromUrl(url) {
    if (!url) {
        return url;
    }
    // 正则表达式用于提取路径
    const regex = /^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n?]+)\/?([^?\n]*)/i;
    const match = url.match(regex);
    // match[2] 包含路径
    return match && match[2] ? '/' + match[2] : '';
}

// 支持的编码列表
const supportedEncodings = ['utf-8', 'utf8', 'unicode-1-1-utf-8', 'gbk', 'gb2312', 'gb-2312', 'CP936', 'MS936'];

function checkEncoding(encoding) {
    if (!supportedEncodings.includes(encoding.toLowerCase())) {
        throw new Error(`Encoding ${encoding} is not supported.`);
    }
}

/**
 * 延时方法
 * @param {*} duration 延时的时长，单位毫秒
 * @param {*} computation 计算的方法，可不填
 * @returns 返回计算结果 Promise 对象
 */
function delayed(duration, computation) {
    return new Promise((resolve, reject) => {
        if (typeof computation !== 'function' && computation !== undefined) {
            reject(new Error('The computation parameter must be a function or undefined.'));
            return;
        }

        mySetTimeout(() => {
            try {
                if (typeof computation === 'function') {
                    resolve(computation());
                } else {
                    resolve();
                }
            } catch (error) {
                reject(error);
            }
        }, duration);
    });
}

/**
 * 封装的timeout方法，可设置promise对象超时时长
 * @param {*} ms 毫秒
 * @param {*} promise promise对象
 * @returns
 */
function timeoutPromise(ms, promise) {
    return new Promise((resolve, reject) => {
        const timeoutId = mySetTimeout(() => {
            reject(new Error("Promise timeout"))
        }, ms);
        promise.then(
            (res) => {
                myClearTimeout(timeoutId);
                resolve(res);
            },
            (err) => {
                myClearTimeout(timeoutId);
                reject(err);
            }
        ).catch((err) => {
            myClearTimeout(timeoutId);
            reject(err);
        });
    })
}

const timers = {};
const hasClearTimeout = typeof clearTimeout !== 'undefined';
let setTimeoutId = 0;

/**
 * 自定义Timeout（由于quickJS没有clearTimeout，所以自己进行实现）
 * @param callback 回调函数
 * @param delay 延时时间，单位毫秒
 * @returns {Number}
 */
function mySetTimeout(callback, delay) {
    if (hasClearTimeout) {
        return setTimeout(callback, delay);
    } else {
        const id = setTimeoutId++;
        setTimeout(() => {
            if (timers[id]) {
                timers[id]();
            }
        }, delay);
        timers[id] = callback;
        return id;
    }
}

/**
 * 清除Timeout任务
 * @param id 任务id
 */
function myClearTimeout(id) {
    if (hasClearTimeout) {
        clearTimeout(id);
    } else {
        delete timers[id];
    }
}

/**
 * 从元素中移除指定的css选择的元素
 * @param {import('cheerio').Cheerio<Element>} element 元素
 * @param cssSelectors css选择器，类型：String | Array<String>
 * @returns {import('cheerio').Cheerio<Element>}
 */
function removeQuerySelector(element, cssSelectors) {

    if (cssSelectors instanceof String) {
        cssSelectors = [cssSelectors];
    }

    if (!cssSelectors instanceof Array) {
        throw new Error("removeQuerySelector参数 'cssSelectors' 必须为String或Array类型");
    }
    for (let cssSelector of cssSelectors) {
        element.find(cssSelector).remove()
    }
    return element;
}

/**
 * 性别类
 */
class Gender {
    constructor(code, name) {
        this.code = code;
        this.name = name;
    }
}

/**
 * 性别枚举
 */
const GenderEnum = {
    unknown: new Gender(0, 'unknown'),
    male: new Gender(1, 'male'),
    female: new Gender(2, 'female'),
    /**
     * 获取性别
     * @param genderCode
     * @param isAll 是否从全部性别中获取，false则unknown转为male
     */
    valueOf(genderCode, isAll) {
        const gender = genderCode === 1 ? this.male : genderCode === 2 ? this.female : this.unknown;
        return isAll ? gender : gender === this.female ? this.female : this.male;
    }
}

/**
 * 通过模板字符串和数据对象生成最终的字符串
 * @param {string} template - 包含模板字符串，其中包含变量或表达式，以 ${...} 形式表示
 * @param {object} data - 包含模板字符串中变量和表达式所需的键值对数据对象
 * @returns {string} - 返回模板字符串中变量和表达式被替换后的最终字符串
 *
 * @example
 * // 定义模板字符串，包含变量和表达式
 * let template = '姓名：${name}，年龄：${age}，明年年龄：${age + 1}';
 *
 * // 定义数据对象
 * let data = { name: '张三', age: 25 };
 *
 * // 调用 formatString 方法生成最终的字符串
 * let result = formatString(template, data);
 *
 * // 输出：'姓名：张三，年龄：25，明年年龄：26'
 * console.log(result);
 */
function formatString(template, data) {
    // 使用 new Function 创建一个新的函数，参数 'data' 用于传入数据对象
    // 使用 with(data) 语句将数据对象中的属性作为局部变量使用
    // 返回执行模板字符串的结果，模板字符串中的变量和表达式会被数据对象中的值替换
    return new Function('data', 'with(data) { return `' + template + '`; }')(data);
}

/**
 * 在当前路径下获取path的绝对路径
 * @param {String} currentPath 当前路径
 * @param {String} path 目标路径 可能是相对路径，也可能是绝对路径
 * @return {String} 返回path的绝对路径
 */
function getAbsPath(currentPath, path) {
    const exampleEndpoint = 'https://example.com';
    // 创建一个新的 URL 对象，基于当前路径
    const base = new URL(currentPath, exampleEndpoint);
    // 创建目标路径的 URL 对象，基于 base 路径
    const absoluteUrl = new URL(path, base);
    // 返回绝对路径
    return absoluteUrl.href.replace(exampleEndpoint, '');
}

export {
    // 第三方依赖
    load,
    he,

    // common中的属性
    UserAgentKey,
    randomUserAgent,
    randomPCUserAgent,
    randomMobileUserAgent,
    htmlToString,
    removeBasedOnSeparator,
    getPathFromUrl,
    checkEncoding,
    delayed,
    timeoutPromise,
    getBaseUrl,
    mySetTimeout,
    myClearTimeout,
    removeQuerySelector,
    Gender,
    GenderEnum,
    removeEmptyLines,
    removeLinesContaining,
    formatString,
    getAbsPath,

    // discover-class
    FilterCategory,
    FilterOption,
    FilterParameter,
    FilterCategoryType,
    DiscoverBook,
    BookLink,
    PageResult,
    DiscoverSource,

    // book-class
    BasicBook,
    Book,
    BasicChapter,
    SearchBookResult,
    BookSource,

    // 爬虫抽象class
    AbstractBookCrawler,
    BookCrawlerTest,
    AbstractDiscoverCrawler,
    DiscoverCrawlerTest,

    // request class
    RequestOptions,
    RequestType,
    RequestMethod,
    DeviceType,
    RequestResponse,
    RequestInputType,
    GetCookiesOptions,
};
